#include "include.h"
#include "../swaptable.h"

int sos_page_free(sos_pcb *pcb, seL4_Word proc_vaddr) {
    int err;
    seL4_Word pd_idx;
    seL4_Word pt_idx;
    sos_page_table *pt;
    page_table_entry *pte;
    seL4_Word sos_vaddr;
    seL4_CPtr proc_cap;

    dprintf(6, "sos_page_free: PID %d freeing page at %p\n",
            pcb->pid, proc_vaddr);

    // Panic if given a bad PD
    conditional_panic(!(pcb->seL4_pd), "PD is uninitialised - process corrupt");

    // Walk the page table - only need to free entries that already exist.
    err = page_walk_nofault(pcb, proc_vaddr, &pte);
    if (err) return err;

    // Pinned frames are not freeable - whoever pinned the frame must free it.
    // This is not an error for the caller of page_free.
    if (pte->attrs.pinned) {
        dprintf(6, "page_free: attempt to free a pinned frame #%d\n",
                pte->framenumber);
        return SOS_PAGE_TABLE_SUCCESS;
    }

    if (!pte->attrs.swapped && pte->framenumber > 0) {
        // Page has been mapped to a physical frame - free it.
        dprintf(6, "sos_page_free: PID %d page %p is at phys %d\n",
                pcb->pid, proc_vaddr, pte->framenumber);

        // Calculate the vaddr for frame_free.
        sos_vaddr = frame_translate_number(pte->framenumber);

        // Grab the process capability for this frame
        err = frame_process_cap(sos_vaddr, pcb, &proc_cap);

        // Check the bookkeeping
        // XXX What conditions would prevent a free?

        // Ungrant the frame
        err = frame_ungrant(sos_vaddr, pcb->cspace, proc_cap);

        // Free the frame
        err = frame_free(sos_vaddr);
        // Panic if frame_free failed, because something is very, very wrong
        // XXX Shared memory: don't panic if grants remain
        if (err) dprintf(6, "frame_free failed (%d)\n", err);
        conditional_panic(err, "Failed to free frame from FT");
    } else if (pte->attrs.swapped && pte->framenumber > 0) {
        // Page has been swapped out. Give the swap number back to the
        // swap table.
        err = sos_swap_free((swap_table_entry) pte->framenumber);
        conditional_panic(err, "Failed to return swap number");
        err = free_swapped_frame(pte->framenumber);
        conditional_panic(err, "Failed to free swapped frame");
    }
    // Else page is fresh - untouched

    // Zero out the PTE
    memset(pte, 0x00, sizeof(struct page_table_entry));

    return SOS_PAGE_TABLE_SUCCESS;
}

/* We still use this when brk or munmap shrinks / frees a region */
int sos_page_free_range(sos_pcb *pcb, seL4_Word proc_vaddr,
                        seL4_Word num_pages) {
    int err;
    int i;

    dprintf(5, "page_free_range: PID %d freeing %d pages from %p\n",
            pcb->pid, num_pages, proc_vaddr);

    for (i = 0; i < num_pages; i++) {
        err = sos_page_free(pcb, proc_vaddr + (PAGE_UNSHIFT(i)));
        if (err == SOS_PAGE_TABLE_FAULT) {
            // Can skip ahead by page number % 1024, to the next PT
            // int left_in_table = 1024 - ((PAGE_SHIFT(proc_vaddr) + i) % 1024);
            // i += left_in_table - 1;
        } else if (err != SOS_PAGE_TABLE_SUCCESS) {
            dprintf(6, "Failed to free page (%d)\n", err);
        }
    }

    dprintf(5, "Free'd %d pages from %p\n", num_pages, (void *) proc_vaddr);
    return SOS_PAGE_TABLE_SUCCESS;
}

/* Free every frame used by the process */
int sos_page_free_all(sos_pcb *pcb) {
    int err = 0;
    sos_region *ptr, *tmp;
    DL_FOREACH_SAFE((pcb->regions), ptr, tmp) {
        sos_print_region(ptr);
        err |= _resize_region(pcb, ptr, 0);
    };
    return err;
}

/* Internal for resizing VM regions */
int _resize_region(sos_pcb *pcb, sos_region *region, seL4_Word npages) {
    int err;
    seL4_Word old_size = region->npages;
    seL4_Word old_base = region->base;

    // free_base is the page after the region's NEW end - free from here on up.
    seL4_Word free_base = old_base + PAGE_UNSHIFT(npages);

    err = sos_resize_region(&(pcb->regions), region, npages);
    if (err != SOS_REGION_SUCCESS) {
        // The new region has run into another.
        // Send the error back upstream.
        return SOS_PAGE_TABLE_INVAL;
    }

    // If the region shrank or was deleted, we need to unmap any dangling pages.
    if (npages < old_size) {
        err = sos_page_free_range(pcb, free_base, old_size - npages);
        return err;
    }

    // If the region grew or stayed the same, we're finished.
    return SOS_PAGE_TABLE_SUCCESS;
}
